package simulator.hardware;

import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Vector;

import simulator.software.ClockInterrupt;
import simulator.software.IOInterrupt;
import simulator.software.Interrupt;
import simulator.software.Kernel;
import simulator.software.Process;
import simulator.software.ProcessState;

/**
 * Computer is the simulator's root abstraction of hardware and software
 * components, including the processor(s) and operating system.
 * 
 * The computer serves as a hub for the simulated components, issuing steps to
 * each concerned sub-component and modeling low level hardware functionality
 * (interrupts, trapping).
 * 
 * @author Kyle Loomis
 * @author Dave LaPointe
 * 
 */
public class Computer
{
   protected List<Processor> processors = new Vector<Processor>();
   protected List<Interrupt> interrupts = new Vector<Interrupt>();
   protected int currentStep;
   protected Kernel kernel;
   protected boolean enableInterrupts = true;
   protected int avgIODuration;

   /**
    * Constructor for <code>Computer</code> that creates a basic hardware
    * context with the number of processors specified.
    * 
    * @param numProcessors
    *           the number of processors this computer has
    */
   public Computer(int numProcessors)
   {
      if (numProcessors > 0)
      {
         for (int i = 0; i < numProcessors; i++)
         {
            processors.add(new Processor(Integer.toString(i)));
         }
      }
      else
      {
         throw new IllegalArgumentException(
               "Number of processors must be greater than 0.");
      }
   }

   /**
    * Gets a <code>List</code> of the <code>Processor</code> objects contained
    * in the computer
    * 
    * @return a <code>List</code> of <code>Processor</code> objects contained in
    *         the computer
    */
   public List<Processor> getProcessors()
   {
      return processors;
   }

   /**
    * Moves the computer state forward one step in time.
    */
   public void step()
   {
      Interrupt i = null;

      kernel.setStatusDescriptor("Ready");

      if (enableInterrupts)
      {
         i = getPendingInterrupt();
      }

      if (i == null)
      {
         for (Processor p : processors)
         {
            p.step();
         }
      }
      else
      {
         System.out.println("Interrupt...");

         for (Processor p : processors)
         {
            p.getHistory().add(kernel);
         }

         kernel.handleInterrupt(i);
      }

      currentStep++;
   }

   private Interrupt getPendingInterrupt()
   {
      Interrupt interrupt = null;

      for (Iterator<Interrupt> iter = interrupts.iterator(); iter.hasNext()
            && interrupt == null;)
      {
         Interrupt i = iter.next();

         if (i.getStep() <= getCurrentStep())
         {
            interrupt = i;
         }
      }

      if (interrupt != null)
      {
         interrupts.remove(interrupt);
      }

      return interrupt;
   }

   /**
    * Schedules the specified <code>Interrupt</code> to occur
    * 
    * @param i
    *           the interrupt to schedule
    */
   public void addInterrupt(Interrupt i)
   {
      interrupts.add(i);
   }

   /**
    * Gets the current simulator time, the number of "steps" the scenario has
    * run so far.
    * 
    * @return the current simulator time, or the number of "steps" run so far.
    */
   public int getCurrentStep()
   {
      return currentStep;
   }

   /**
    * Sets the kernel (operating system) associated with this computer.
    * 
    * @param kernel
    *           the operating system to associate with this computer.
    */
   public void setKernel(Kernel kernel)
   {
      this.kernel = kernel;

      Processor p = getProcessors().get(0);
      p.load(kernel);
   }

   /**
    * Gets the kernel (operating system) associated with this computer.
    * 
    * @return the kernel associated with this computer
    */
   public Kernel getKernel()
   {
      return kernel;
   }

   /**
    * Signals the hardware that the currently running process would like to
    * issue a system call. Typically used to request I/O.
    */
   public void trap()
   {
      System.out.println("Trapping to kernel...");
      Processor p = processors.get(0);
      Process pr = (Process) p.getRunningProcess();
      pr.setState(ProcessState.BLOCKED);

      Random rand = new Random();
      int ioSteps = (int) (rand.nextDouble() * (avgIODuration * 2));

      Interrupt i = new IOInterrupt(getCurrentStep() + ioSteps, pr);
      addInterrupt(i);

      disableInterrupts();
      p.load(kernel);
   }

   /**
    * Signals the underlying hardware that the currently running process is
    * complete.
    */
   public void processComplete()
   {
      System.out.println("Process done!");
      Processor p = processors.get(0);
      Process pr = (Process) p.getRunningProcess();
      pr.setCycleCompleted(currentStep);
      pr.setState(ProcessState.DONE);
      p.load(kernel);
   }

   /**
    * Disables interrupts from being handled. Interrupts that occur while
    * disabled will be handled in order of arrival when re-enabled.
    */
   public void disableInterrupts()
   {
      enableInterrupts = false;
   }

   /**
    * Enables interrupt handling.
    */
   public void enableInterrupts()
   {
      enableInterrupts = true;
   }

   /**
    * Clears all pending clock interrupts.
    */
   public void clearClockInterrupts()
   {
      Vector<Interrupt> toDelete = new Vector<Interrupt>();

      for (Interrupt i : interrupts)
      {
         if (i instanceof ClockInterrupt)
         {
            toDelete.add(i);
         }
      }

      for (Interrupt i : toDelete)
      {
         interrupts.remove(i);
      }
   }

   /**
    * Sets the average number of steps a process has to wait before an I/O
    * operation completes
    * 
    * @param avgIODuration
    *           the average number of steps a process has to wait before an I/O
    *           operation completes
    */
   public void setAverageIODuration(int avgIODuration)
   {
      this.avgIODuration = avgIODuration;
   }
}